\section{【实现】分析内核函数调用关系}\label{ux5b9eux73b0ux5206ux6790ux5185ux6838ux51fdux6570ux8c03ux7528ux5173ux7cfb}

首先，ucore需要建立一个空的栈空间，然后才能进行函数调用、参数传递等处理工作。ucore是在哪里建立的栈呢？其实ucore是借用了bootloader的栈空间，而bootloader在bootasm.S中的如下语句建立的栈空间：
\# Set up the stack pointer and call into C. movl \$0x0, \%ebp movl
\$start, \%esp

可以看到bootloader把栈底设置到了\$start地址处，正好是bootloader的起始地址0x7c00。不过由于入栈操作中的esp是向下增长的，所以不会覆盖bootloader的内容。那ebp有何作用呢？我们先暂时放在一边，继续跟踪代码的执行。

接下来，bootloader会调用bootmain（）函数，而bootmain（）函数会在加载完ucore后，调用ucore的起始函数kern\_init（）。在ucore的继续执行过程中，还将有如下的函数调用过程：

\begin{lstlisting}
kern_init-->monitor-->runcmd-->mon_backtrace-->print_stackframe。
\end{lstlisting}

这通过看源代码或执行monitor中的backtrace命令都可以了解到。

我们可以结合前面的实验来说明ucore是如何分析出这样的调用关系的。

操作系统中的中断（也称异常）技术是操作系统的重要功能，是计算机硬件和软件相配合产生的重要技术。简单地说，中断处理是指由于有紧急事件产生，需要打断当前CPU的正常执行，转而处理紧急事件，处理完毕后，恢复到被打断的地方继续执行。通过中断机制，计算机系统可以高效地处理外设请求，可以快速响应应用软件的异常或请求，也可以有规律地打断应用程序的执行，把执行CPU控制权还到操作系统手中，从而使得整个计算机系统的资源可控。但单纯的操作系统原理书籍很难深入分析中断的处理细节。我们希望通过后续的proj4/4.1.1等的实验，让读者了解到ucore操作系统如何完成上述事情。

首先我们需要了解GCC生成的C函数调用过程： 1.
调用函数为了传递参数给被调用函数，需要执行0到n个push指令把函数参数入栈，然后会执行一个call指令，在call指令内部执行过程中，还把返回地址（即CALL指令下一条指令的地址）也入栈了。
2.
GCC编译器会在每个函数的起始部分插入类似如下指令(可参看obj/kernel.asm文件内容)：

\begin{lstlisting}
    push   %ebp
    mov    %esp,%ebp
    sub    $NUM,%esp
\end{lstlisting}

在ucore执行到一个函数的函数体时，已经有以下数据顺序入栈：调用函数的参数，函数返回地址。由此得到类似如下的栈结构（参数入栈顺序跟调用方式有关，
这里以C语言默认的CDECL为例）：

\begin{lstlisting}
高地址方向
|------------------------|
|------------------------| <-----------
|     .........       |
|   argument 3     |  Caller's stack frame
|   argument 2     |
|   argument 1     |
|   return address   |<---------- esp
|------------------------|
|     .........       |
低地址方向
\end{lstlisting}

``push \%ebp''和``mov
\%esp,\%ebp''这两条指令实在隐含了对函数调用关系链的建立：首先将ebp入栈，然后将栈顶指针esp赋值给ebp，此时的栈结构如下所示：

高地址方向 \textbar{}------------------------\textbar{}
\textbar{}------------------------\textless{}----------- \textbar{}
\ldots{}\ldots{}\ldots{} \textbar{} \textbar{} argument 3 \textbar{}
Caller's stack frame \textbar{} argument 2 \textbar{} \textbar{}
argument 1 \textbar{} \textbar{} return address
\textbar{}\textless{}----------- \textbar{}----previous
ebp-----\textbar{}\textless{}------ ebp, esp
\textbar{}------------------------\textbar{} \textbar{}
\ldots{}\ldots{}\ldots{} \textbar{} 低地址方向

``mov
\%esp,\%ebp''这条指令表面上看是用esp把ebp的旧值覆盖了，但在这条语句之前，ebp旧值已经被压栈（位于栈顶），而新的ebp又恰恰指向栈顶。第三条语句``sub
\$NUM,\%esp''把esp减少了NUM个值，这其实是建立了函数的局部变量、寄存器保存的空间。此时的栈结构如下所示：

\begin{lstlisting}
高地址方向
|------------------------|
|------------------------<-----------
|     .........       |
|   argument 3     |  Caller's stack frame
|   argument 2     |
|   argument 1     |
|   return address  |<-----------
|----previous ebp-----|<------ ebp, esp
|------------------------|
|     .........       |
|   saved registers  |
|     .........       | Current(Callee's) stacl frame
|  local  variables  |
|     …......       |
|----------------------- |<-------- esp
|------------------------|
|     .........       |
低地址方向
\end{lstlisting}

到此时为止，ebp寄存器处于函数调用关系链中一个非常重要的地位。ebp寄存器中存储着栈中的一个地址（栈帧分界处），此地址是``老''ebp入栈后的栈顶。那么以该该地址为基准，向高地址方向（即栈底方向）能获取返回地址、参数值，向低地址方向（栈顶方向）能获取函数局部变量值，而该地址处又存放着上一层函数调用时的ebp值。由于ebp中的地址处总是``上一层函数调用时的ebp值''，这样，就能通过把ebp的内容作为寻找上一个调用函数的栈帧的指针，如此形成递归，直至到达栈底。这就可找到整个的函数调用栈。这也是kdebug.c中print\_stackframe函数的实现内容。
